1 module hip.api.data.font; 2 public import hip.api.renderer.texture; 3 4 5 alias HipCharKerning = int[dchar]; 6 alias HipFontKerning = HipCharKerning[dchar]; 7 ///see hip.graphics.g2d.textrenderer 8 struct HipTextRendererVertexAPI 9 { 10 float[3] vPosition = [0,0,0]; 11 float[2] vTexST = [0,0]; 12 } 13 14 /** 15 * A text information that is returned from the word wrap range. 16 * Beyond the information with line and width, it also has a cache. 17 * This cache is used for optimization in both kerning and associative array 18 * lookup. This can make a big difference by having a single lookup instead of 19 * 2. The lookup is the slowest part of text rendering, which makes this a lot faster. 20 */ 21 struct HipLineInfo 22 { 23 dstring line; 24 int width; 25 const(HipFontChar)*[512] fontCharCache; 26 int[512] kerningCache; 27 } 28 29 struct HipWordWrapRange 30 { 31 private dstring inputText; 32 private IHipFont font; 33 private int maxWidth, currIndex; 34 private HipLineInfo currLine; 35 private bool hasFinished; 36 37 this(dstring inputText, IHipFont font, int maxWidth) 38 { 39 this.inputText = inputText; 40 this.font = font; 41 this.maxWidth = maxWidth <= 0 ? int.max : maxWidth; 42 } 43 44 bool empty(){return hasFinished;} 45 /** 46 * Every time it pops the front, it will search for words. Words are defined as text delimited 47 * by spaces. If a word is bigger than the max width, it will be cutten and the word will spam 48 * through multiple lines. 49 */ 50 void popFront() 51 { 52 const(HipFontChar)* ch, next; 53 int currWidth = 0, wordWidth = 0, wordStartIndex = currIndex; 54 for(int i = currIndex, it = 0; i < inputText.length; i++, it++) 55 { 56 if(ch is null) 57 { 58 ch = inputText[i] in font.characters; 59 if(ch is null) 60 { 61 currLine.kerningCache[it] = 0; 62 currLine.fontCharCache[it] = null; 63 continue; 64 } 65 } 66 int kern = 0; 67 if(i + 1 < inputText.length) 68 { 69 next = inputText[i+1] in font.characters; 70 if(next) kern = font.getKerning(ch, next); 71 } 72 currLine.kerningCache[it] = kern; 73 currLine.fontCharCache[it] = ch; 74 switch(inputText[i]) 75 { 76 case '\n': 77 currLine.line = inputText[currIndex..i]; 78 currLine.width = currWidth; 79 currIndex = i+1; 80 return; 81 case ' ': 82 if(font.spaceWidth + wordWidth + currWidth > maxWidth) 83 { 84 currLine.line = inputText[currIndex..i]; 85 currLine.width = currWidth+wordWidth; 86 currIndex = i+1; 87 return; 88 } 89 else 90 { 91 currWidth+= wordWidth + font.spaceWidth; 92 wordStartIndex = i; 93 wordWidth = 0; 94 } 95 break; 96 default: 97 if(wordWidth + ch.xadvance + kern + currWidth > maxWidth) 98 { 99 if(wordStartIndex == currIndex) 100 { 101 currWidth = wordWidth; 102 wordStartIndex = i; 103 } 104 ///Subtract one for ignoring the space. 105 currLine.line = inputText[currIndex..wordStartIndex]; 106 currLine.width = currWidth; 107 if(wordStartIndex < inputText.length && inputText[wordStartIndex] == ' ') 108 wordStartIndex++; 109 currIndex = wordStartIndex; 110 return; 111 } 112 wordWidth += ch.xadvance + kern; 113 break; 114 } 115 ch = next; 116 } 117 if(currIndex < inputText.length && inputText[currIndex] == ' ') 118 currIndex++; 119 currLine.line = inputText[currIndex..$]; 120 currLine.width = currWidth+wordWidth; 121 currIndex = cast(int)inputText.length; 122 if(currLine.line.length == 0) hasFinished = true; 123 } 124 125 HipLineInfo front() 126 { 127 if(currIndex == 0) popFront(); 128 return currLine; 129 } 130 } 131 132 struct HipFontChar 133 { 134 uint id; 135 ///Those are in absolute values 136 int x, y, width, height; 137 138 int xoffset, yoffset, xadvance, page, chnl; 139 140 ///Normalized values 141 float normalizedX, normalizedY, normalizedWidth, normalizedHeight; 142 int glyphIndex; 143 void putCharacterQuad(float x, float y, float depth, HipTextRendererVertexAPI[] quad) const 144 { 145 //Gen vertices 146 //Top left 147 quad[0] = HipTextRendererVertexAPI( 148 [x, y, depth], 149 [normalizedX, normalizedY] //ST 150 ); 151 //Top Right 152 quad[1] = HipTextRendererVertexAPI( 153 [x+width, y,depth], 154 [normalizedX + normalizedWidth, normalizedY] //S + Wnorm, T 155 ); 156 //Bot right 157 quad[2] = HipTextRendererVertexAPI( 158 [x+ width, y +height, depth], 159 [ 160 normalizedX + normalizedWidth, //S+Wnorm 161 normalizedY + normalizedHeight //T+Hnorm 162 ] 163 ); 164 //Bot left 165 quad[3] = HipTextRendererVertexAPI( 166 [x, y + height, depth], 167 [normalizedX, normalizedY + normalizedHeight] // S, T+Hnorm 168 ); 169 } 170 } 171 172 interface IHipFont 173 { 174 int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const; 175 int getKerning(dchar current, dchar next) const; 176 /** 177 * 178 * Params: 179 * text = The text 180 * linesWidths = Save width per line 181 * biggestWidth = The biggest width in lines 182 * height = Height of all the lines together 183 * maxWidth = If maxWidth != -1, it will break the text into lines automatically. 184 */ 185 void calculateTextBounds(in dstring text, ref uint[] linesWidths, out int biggestWidth, out int height, int maxWidth = -1) const; 186 HipWordWrapRange wordWrapRange(dstring text, int maxWidth) const; 187 ref HipFontChar[dchar] characters(); 188 ref IHipTexture texture(); 189 uint spaceWidth() const; 190 uint spaceWidth(uint newWidth); 191 uint lineBreakHeight() const; 192 uint lineBreakHeight(uint newHeight); 193 194 } 195 196 abstract class HipFont : IHipFont 197 { 198 199 abstract int getKerning(dchar current, dchar next) const; 200 abstract int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const; 201 202 ///Underlying GPU texture 203 IHipTexture _texture; 204 HipFontChar[dchar] _characters; 205 ///Saves the space width for the bitmap text process the ' '. If the original spaceWidth is == 0, it won't draw a quad 206 uint _spaceWidth; 207 ///How much the line break will offset in Y the next char 208 uint _lineBreakHeight; 209 210 ///////Properties/////// 211 final ref HipFontChar[dchar] characters(){return _characters;} 212 final ref const(HipFontChar[dchar]) characters() const {return _characters;} 213 final ref IHipTexture texture(){return _texture;} 214 final uint spaceWidth() const {return _spaceWidth;} 215 final uint spaceWidth(uint newWidth){return _spaceWidth = newWidth;} 216 final uint lineBreakHeight() const {return _lineBreakHeight;} 217 final uint lineBreakHeight(uint newHeight){return _lineBreakHeight = newHeight;} 218 219 final HipWordWrapRange wordWrapRange(dstring text, int maxWidth) const 220 { 221 return HipWordWrapRange(text, cast(IHipFont)this, maxWidth); 222 } 223 224 225 final void calculateTextBounds(in dstring text, ref uint[] linesWidths, out int biggestWidth, out int height, int maxWidth = -1) const 226 { 227 int i = 0; 228 foreach(HipLineInfo lineInfo; wordWrapRange(text, maxWidth)) 229 { 230 if(lineInfo.width > biggestWidth) biggestWidth = lineInfo.width; 231 if(linesWidths.length < i+1) 232 linesWidths.length++; 233 linesWidths[i++] = lineInfo.width; 234 } 235 height = lineBreakHeight*i; 236 } 237 HipFont getFontWithSize(uint size); 238 239 }